package org.restorator.dao.tx;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.Callable;

import javax.sql.DataSource;

import org.restorator.exception.DataStoreException;
import org.restorator.exception.TransactionException;
import org.restorator.utill.GeneralUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.google.common.base.Preconditions.*;

/**
 *
 * @author Snisar Bogdan.
 */
public class TransactionManagerJdbc implements TransactionManager {

    private static volatile TransactionManagerJdbc INSTANCE;

    public static TransactionManager getInstance() {
        TransactionManagerJdbc tmp = INSTANCE;
        if(tmp == null) {
            synchronized (TransactionManagerJdbc.class) {
                tmp = INSTANCE;
                if(tmp == null) {
                    tmp = INSTANCE = new TransactionManagerJdbc();
                }
            }
        }
        return tmp;
    }




    private TransactionManagerJdbc() {
        try {
//            dataSourceFactory =  new DbcpDataSourceProvider();
            dataSourceFactory = new C3PoDataSourceFactory();
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private TransactionManagerJdbc(DataSourceFactory dataSourceFactory) {
        this.dataSourceFactory = dataSourceFactory;
    }

    public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
        this.dataSourceFactory = dataSourceFactory;
    }

    private static Logger logger = LoggerFactory.getLogger(GeneralUtil.getClassName());
    private static final ThreadLocal<Connection> CONNECT_STORE = new ThreadLocal<>();

    private DataSourceFactory dataSourceFactory;
    private DataSource dataSource;




    private void init() throws DataStoreException {
        checkState(dataSourceFactory != null, "Illegal init");
        dataSource = dataSourceFactory.getDataSource();
    }

    @Override
    public Connection getCurrentConnection() throws DataStoreException {
        Connection c = CONNECT_STORE.get();
        logger.trace("get connection in thread");
        if(c == null) {
            try {
                logger.trace("no connection, try get new");
                c = dataSource.getConnection();
                CONNECT_STORE.set(c);
                logger.trace("connection get");
                return c;
            } catch (SQLException e) {
                throw new DataStoreException(e);
            }
        } else {
            return c;
        }
    }

    @Override
    public <E> E doInTran(Callable<E> unitOfWork) throws TransactionException {
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            logger.trace("Get connection in dataSource {}", connection);
            connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            CONNECT_STORE.set(connection);

            E result = unitOfWork.call();
            logger.trace("Do unit of work, and get result {}", result);
            connection.commit();
            return result;
        } catch (Exception e) {
            logger.warn("Exception in transaction: {}. Try to rollback! StackTrace {}", e.getMessage(), e);
            rollBack(connection);
        throw new TransactionException("Exception in transaction", e);
        } finally {
            if(connection != null) {
                closeConnect(connection);
            }
        }

    }

    private void rollBack(Connection connection) {
        if (connection != null) {
            try {
                connection.rollback();
            } catch (SQLException e1) {
                logger.error("Exception in rollback! StackTrace {}", e1);
            }
        }
    }

    private void closeConnect(Connection connection) {
        try {
            logger.debug("Closing connection");
            connection.close();
        } catch (SQLException e) {
            logger.error("Exception in close connection: {}", e.getMessage());
        }
    }

}
